﻿using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WCAD.Kean
{
    public class Textformations
    {
        [CommandMethod("MTIB")]
        public static void MTIB()
        {
            var doc = Application.DocumentManager.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;
            // Start by getting the text (or other) object in the block
            var pneo = new PromptNestedEntityOptions("\nSelect text inside block");
            var pner = ed.GetNestedEntity(pneo);
            if (pner.Status != PromptStatus.OK)
                return;
            var selId = pner.ObjectId;
            // Start a transaction to access the object
            var oc = selId.ObjectClass;
            if (!oc.IsDerivedFrom(RXClass.GetClass(typeof(DBText))) && !oc.IsDerivedFrom(RXClass.GetClass(typeof(MText))))
            {
                // Isn't a text object - ask whether we continue
                ed.WriteMessage("\nObject is not text, it is a {0}.", oc.Name);
                var pko = new PromptKeywordOptions("\nDo you want to continue? [Yes/No]", "Yes No");
                pko.AppendKeywordsToMessage = true;
                pko.AllowNone = true;
                pko.Keywords.Default = "No";
                var pr = ed.GetKeywords(pko);
                if (pr.Status != PromptStatus.OK || pr.StringResult == "No")
                    return;
            }
            // Now get the displacement from the picked point
            var ppo = new PromptPointOptions("\nEnter displacement");
            ppo.BasePoint = pner.PickedPoint;
            ppo.UseBasePoint = true;
            var ppr = ed.GetPoint(ppo);
            if (ppr.Status != PromptStatus.OK)
                return;
            // Start a transaction to modify the object
            using (var tr = doc.TransactionManager.StartTransaction())
            {
                // Get the displacement from the picked point to the newly
                // selected one
                var disp = ppr.Value - pner.PickedPoint;
                var mat = Matrix3d.Displacement(disp);
                // Unless we get a block reference container, use the
                // identity matrix as the block transform
                var brMat = Matrix3d.Identity;
                // Get the containers around the nested entity
                var conts = pner.GetContainers();
                foreach (var id in conts)
                {
                    var br = tr.GetObject(id, OpenMode.ForRead) as BlockReference;
                    if (br != null)
                    {
                        brMat = brMat.PreMultiplyBy(br.BlockTransform);
                    }
                }
                // To get the transformation in world coordinates rather
                // than block coordinates we post-multiply by block
                // transform and pre-multiply by its inverse
                mat = mat.PreMultiplyBy(brMat.Inverse());
                mat = mat.PostMultiplyBy(brMat);
                // Transform the entity
                var ent = (Entity)tr.GetObject(selId, OpenMode.ForWrite);
                ent.TransformBy(mat);
                // Open each of the containers and set a property so that
                // they each get regenerated
                foreach (var id in conts)
                {
                    var ent2 = tr.GetObject(id, OpenMode.ForWrite) as Entity;
                    if (ent2 != null)
                    {
                        // We might also have called this method:
                        // ent2.RecordGraphicsModified(true);
                        // but setting a property works better with undo
                        ent2.Visible = ent2.Visible;
                    }
                }
                tr.Commit();
            }
        }

        public class DisplacementJig : EntityJig
        {
            private Point3d _pos;
            private Point3d _loc;
            public DisplacementJig(Entity ent, Point3d basePt) : base(ent)
            {
                _loc = basePt;
            }
            protected override bool Update()
            {
                var disp = _pos - _loc;
                _loc = _pos;
                var mat = Matrix3d.Displacement(disp);
                Entity.TransformBy(mat);
                return true;
            }
            protected override SamplerStatus Sampler(JigPrompts prompts)
            {
                var opts = new JigPromptPointOptions("\nSelect displacement");
                opts.BasePoint = _pos;
                opts.UserInputControls = UserInputControls.NoZeroResponseAccepted;
                var ppr = prompts.AcquirePoint(opts);
                if (_pos == ppr.Value)
                    return SamplerStatus.NoChange;
                _pos = ppr.Value;
                return SamplerStatus.OK;
            }
        }

        [CommandMethod("MTIBJIG")]
        public static void MTIBJIG()
        {
            var doc = Application.DocumentManager.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;
            // Start by getting the text (or other) object in the block
            var pneo =new PromptNestedEntityOptions("\nSelect text inside block");
            var pner = ed.GetNestedEntity(pneo);
            if (pner.Status != PromptStatus.OK)
                return;
            var selId = pner.ObjectId;
            // Start a transaction to access the object
            var oc = selId.ObjectClass;
            if (!oc.IsDerivedFrom(RXClass.GetClass(typeof(DBText))) &&!oc.IsDerivedFrom(RXClass.GetClass(typeof(MText))))
            {
                // Isn't a text object - ask whether we continue
                ed.WriteMessage("\nObject is not text, it is a {0}.", oc.Name);
                var pko =new PromptKeywordOptions("\nDo you want to continue? [Yes/No]", "Yes No");
                pko.AppendKeywordsToMessage = true;
                pko.AllowNone = true;
                pko.Keywords.Default = "No";
                var pr = ed.GetKeywords(pko);
                if (pr.Status != PromptStatus.OK || pr.StringResult == "No")
                    return;
            }
            // Start a transaction to modify the object
            using (var tr = doc.TransactionManager.StartTransaction())
            {
                // Unless we get a block reference container, use the
                // identity matrix as the block transform
                var brMat = Matrix3d.Identity;
                // Get the containers around the nested entity
                var conts = pner.GetContainers();
                foreach (var brId in conts)
                {
                    var br =tr.GetObject(brId, OpenMode.ForRead) as BlockReference;
                    if (br != null)
                    {
                        brMat = brMat.PreMultiplyBy(br.BlockTransform);
                    }
                }
                // Transform the entity
                var ent = (Entity)tr.GetObject(selId, OpenMode.ForWrite);
                // Before we run the jig, transform the object by the
                // aggregate transform of the containers
                ent.TransformBy(brMat);
                // Run the jig to displace the object
                var dj = new DisplacementJig(ent, pner.PickedPoint);
                var pr = ed.Drag(dj);
                if (pr.Status == PromptStatus.OK)
                {
                    // We can transform the entity back, now (at least in
                    // terms of the containers: the displacement remains)
                    ent.TransformBy(brMat.Inverse());
                    // Open each of the containers and set a property so that
                    // they each get regenerated
                    foreach (var id in conts)
                    {
                        var ent2 = tr.GetObject(id, OpenMode.ForWrite) as Entity;
                        if (ent2 != null)
                        {
                            // We might also have called this method:
                            // ent2.RecordGraphicsModified(true);
                            // but setting a property works better with undo
                            ent2.Visible = ent2.Visible;
                        }
                    }
                    tr.Commit();
                }
            }
        }
    }
}
